package tcp;

import java.io.IOException;
import java.net.Socket;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

/**
 * This version of TCP server runs in only one thread of execution.
 * This keeps it from blocking other things while sending.
 * Simple iteration is used to go through each session handler.
 * 
 * @author Kristopher L. Kalish, James Madison University
 * @version 1 - April 10, 2008
 * @version 2 - April 16, 2008 (Added NewSessionSubject, broke up code)
 *
 */
public class TCPServer implements NewConnectionObserver, NewSessionSubject, Runnable
{
	public static final int DEFAULT_LISTEN_PORT   = 2010;
	public static final int ALTERNATE_LISTEN_PORT = 2011;
	
	protected volatile boolean       running;
	protected List<SessionHandler>   sessions;
	private int                      listenPort;
	private List<Object>             objectsToSend;
	private List<NewSessionObserver> observers;
	private Thread                   serverThread;
	
	protected Object lock = new Object();
	
	public TCPServer()
	{
		this(DEFAULT_LISTEN_PORT);
	}
	
	public TCPServer(int listenPort)
	{
		this.listenPort = listenPort;
		
		objectsToSend =
			new LinkedList<Object>();
			 //Collections.synchronizedList(new LinkedList<Object>());
		
		sessions =
			 Collections.synchronizedList(new LinkedList<SessionHandler>());
		
		observers =
			 Collections.synchronizedList(new LinkedList<NewSessionObserver>());
		
		System.out.println("Creating connection listener...");
		ConnectionListener cl = new ConnectionListener(listenPort);
		cl.start();
		cl.addNewConnectionObserver(this);
	}
	
	public void addNewSessionObserver(NewSessionObserver o)
	{
		observers.add(o);
	}
	
	public int getListenPort()
	{
		return listenPort;
	}
	
	public void handleNewConnection(Socket s)
	{
		try
		{
			SessionHandler sh = new SessionHandler(s);
			sessions.add(sh);
			notifyObservers(sh);
		}
		catch(IOException ioe) 
		{
			ioe.printStackTrace();
		}
	}
	
	public void notifyObservers(SessionHandler sh)
	{
		for(NewSessionObserver observer : observers) 
			observer.handleNewSession(sh);
	}
	
	protected void performSendAll() throws IOException
	{
		for(Object obj : objectsToSend)
		{
			System.out.println("Sending " + obj + " in TCPServer run()."); //debug

			for(SessionHandler session : sessions)
				session.send(obj);
		}
		
		objectsToSend.clear();
	}
	
	public void removeNewSessionObserver(NewSessionObserver o)
	{
		observers.remove(o);
	}
	
	public void run()
	{
		while(running)
		{
			synchronized(lock)
			{
				try 
				{
					performSendAll();
				}
				catch(IOException ioe)
				{
					ioe.printStackTrace();
				}
				
				try
				{
					lock.wait();
				}
				catch(InterruptedException ie)
				{
					ie.printStackTrace();
				}
			}
		}
		
		// we finished running, clean up
		try
		{
			for(SessionHandler session : sessions)
				session.cleanup();
		}
		catch (IOException ioe)
		{
			ioe.printStackTrace();
		}
	}
	
	public void sendToAll(Object data)
	{
		synchronized(lock)
		{
			objectsToSend.add(data);
			lock.notifyAll();
		}
	}
	
	public void start()
	{
		if(serverThread == null)
		{
			running      = true;
			serverThread = new Thread(this);
			serverThread.start();
		}
	}
}
